// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 软件包协同设计为
// Mach-O文件的特殊代码签名。
// None
// 这不是用于代码签名的通用工具。它是制造的
// 专门针对Go工具链。它使用相同的
// 作为Darwin链接器的ad-hoc签名算法。
package codesign

import (
	"crypto/sha256"
	"debug/macho"
	"encoding/binary"
	"io"
)

// 代码签名布局。
// None
// 代码签名是一个字节块，包含
// 包含一个或多个Blob的超级Blob。临时的
// 签名时，单个CodeDirectory Blob就足够了。
// None
// 超级blob从它的头（二进制表示）开始
// ，然后是一个列表（在我们的例子中，
// 一）水滴（偏移量和大小）。一个CodeDirectory Blob启动
// 其头部（CodeDirectory结构的二进制表示形式），
// 后跟标识符（作为C字符串）和哈希，在
// 相应的偏移量。
// None
// 签名数据必须包含在_LINKEDIT段中。
// 在Mach-O文件头中，一个LC_代码_签名加载命令
// 指向数据。

const (
	pageSizeBits = 12
	pageSize     = 1 << pageSizeBits
)

const LC_CODE_SIGNATURE = 0x1d

// 常量和结构布局来自
// https:

const (
	CSMAGIC_REQUIREMENT        = 0xfade0c00 // 单需求blob
	CSMAGIC_REQUIREMENTS       = 0xfade0c01 // 需求向量（内部需求）
	CSMAGIC_CODEDIRECTORY      = 0xfade0c02 // 代码目录blob
	CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // 签名数据的嵌入形式
	CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // 嵌入签名的多拱集合

	CSSLOT_CODEDIRECTORY = 0 // 代码目录的插槽索引
)

const (
	CS_HASHTYPE_SHA1             = 1
	CS_HASHTYPE_SHA256           = 2
	CS_HASHTYPE_SHA256_TRUNCATED = 3
	CS_HASHTYPE_SHA384           = 4
)

const (
	CS_EXECSEG_MAIN_BINARY     = 0x1   // 可执行段表示主二进制文件
	CS_EXECSEG_ALLOW_UNSIGNED  = 0x10  // 允许未签名页（用于调试）
	CS_EXECSEG_DEBUGGER        = 0x20  // 主二进制文件是调试器
	CS_EXECSEG_JIT             = 0x40  // 启用JIT
	CS_EXECSEG_SKIP_LV         = 0x80  // 跳过库验证
	CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // 可以执行cdhash
	CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // 可以执行cdhash
)

type Blob struct {
	typ    uint32 // 条目类型
	offset uint32 // 分录抵销
	// 数据如下
}

func (b *Blob) put(out []byte) []byte {
	out = put32be(out, b.typ)
	out = put32be(out, b.offset)
	return out
}

const blobSize = 2 * 4

type SuperBlob struct {
	magic  uint32 // 幻数
	length uint32 // 超团总长度
	count  uint32 // 下列索引项的数目
	// 水滴
}

func (s *SuperBlob) put(out []byte) []byte {
	out = put32be(out, s.magic)
	out = put32be(out, s.length)
	out = put32be(out, s.count)
	return out
}

const superBlobSize = 3 * 4

type CodeDirectory struct {
	magic         uint32 // 幻数（CSMAGIC_代码目录）
	length        uint32 // CodeDirectory blob的总长度
	version       uint32 // 兼容版本
	flags         uint32 // 设置和模式标志
	hashOffset    uint32 // 散列槽元素在索引0处的偏移量
	identOffset   uint32 // 标识符字符串的偏移量
	nSpecialSlots uint32 // 特殊哈希槽数
	nCodeSlots    uint32 // 普通（代码）哈希槽的数量
	codeLimit     uint32 // 对主图像签名范围的限制
	hashSize      uint8  // 每个哈希的大小（以字节为单位）
	hashType      uint8  // 哈希类型（cdHashType*常量）
	_pad1         uint8  // 未使用（必须为零）
	pageSize      uint8  // log2（以字节为单位的页面大小）；0=>无限
	_pad2         uint32 // 未使用（必须为零）
	scatterOffset uint32
	teamOffset    uint32
	_pad3         uint32
	codeLimit64   uint64
	execSegBase   uint64
	execSegLimit  uint64
	execSegFlags  uint64
	// 数据如下
}

func (c *CodeDirectory) put(out []byte) []byte {
	out = put32be(out, c.magic)
	out = put32be(out, c.length)
	out = put32be(out, c.version)
	out = put32be(out, c.flags)
	out = put32be(out, c.hashOffset)
	out = put32be(out, c.identOffset)
	out = put32be(out, c.nSpecialSlots)
	out = put32be(out, c.nCodeSlots)
	out = put32be(out, c.codeLimit)
	out = put8(out, c.hashSize)
	out = put8(out, c.hashType)
	out = put8(out, c._pad1)
	out = put8(out, c.pageSize)
	out = put32be(out, c._pad2)
	out = put32be(out, c.scatterOffset)
	out = put32be(out, c.teamOffset)
	out = put32be(out, c._pad3)
	out = put64be(out, c.codeLimit64)
	out = put64be(out, c.execSegBase)
	out = put64be(out, c.execSegLimit)
	out = put64be(out, c.execSegFlags)
	return out
}

const codeDirectorySize = 13*4 + 4 + 4*8

// CodeSigCmd是Mach-O LC_代码_签名加载命令。
type CodeSigCmd struct {
	Cmd      uint32 // 信用证代码签名
	Cmdsize  uint32 // 此命令的大小（16）
	Dataoff  uint32 // _uulinkedit段中数据的文件偏移量
	Datasize uint32 // _uulinkedit段中数据的文件大小
}

func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) {
	get32 := f.ByteOrder.Uint32
	for _, l := range f.Loads {
		data := l.Raw()
		cmd := get32(data)
		if cmd == LC_CODE_SIGNATURE {
			return CodeSigCmd{
				cmd,
				get32(data[4:]),
				get32(data[8:]),
				get32(data[12:]),
			}, true
		}
	}
	return CodeSigCmd{}, false
}

func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
func put8(b []byte, x uint8) []byte     { b[0] = x; return b[1:] }
func puts(b, s []byte) []byte           { n := copy(b, s); return b[n:] }

// Size计算代码签名的大小。
// id是用于签名的标识符（CodeDirectory blob中的一个字段
// 在临时签名中没有意义）。
func Size(codeSize int64, id string) int64 {
	nhashes := (codeSize + pageSize - 1) / pageSize
	idOff := int64(codeDirectorySize)
	hashOff := idOff + int64(len(id)+1)
	cdirSz := hashOff + nhashes*sha256.Size
	return int64(superBlobSize+blobSize) + cdirSz
}

// Sign生成一个特殊代码签名并将其写入out。
// out必须至少具有长度大小（codeSize，id）。
// 数据是不带签名的文件内容，大小为codeSize。
// textOff和textSize是文本段的文件偏移量和大小。
// 如果这是主可执行文件，则isMain为true。
// id是用于签名的标识符（CodeDirectory blob中的一个字段
// 在临时签名中没有意义）。
func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
	nhashes := (codeSize + pageSize - 1) / pageSize
	idOff := int64(codeDirectorySize)
	hashOff := idOff + int64(len(id)+1)
	sz := Size(codeSize, id)

	// 发出blob头
	sb := SuperBlob{
		magic:  CSMAGIC_EMBEDDED_SIGNATURE,
		length: uint32(sz),
		count:  1,
	}
	blob := Blob{
		typ:    CSSLOT_CODEDIRECTORY,
		offset: superBlobSize + blobSize,
	}
	cdir := CodeDirectory{
		magic:        CSMAGIC_CODEDIRECTORY,
		length:       uint32(sz) - (superBlobSize + blobSize),
		version:      0x20400,
		flags:        0x20002, // 临时链接已签名
		hashOffset:   uint32(hashOff),
		identOffset:  uint32(idOff),
		nCodeSlots:   uint32(nhashes),
		codeLimit:    uint32(codeSize),
		hashSize:     sha256.Size,
		hashType:     CS_HASHTYPE_SHA256,
		pageSize:     uint8(pageSizeBits),
		execSegBase:  uint64(textOff),
		execSegLimit: uint64(textSize),
	}
	if isMain {
		cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
	}

	outp := out
	outp = sb.put(outp)
	outp = blob.put(outp)
	outp = cdir.put(outp)

	// 发出标识符
	outp = puts(outp, []byte(id+"\000"))

	// 发出散列
	var buf [pageSize]byte
	h := sha256.New()
	p := 0
	for p < int(codeSize) {
		n, err := io.ReadFull(data, buf[:])
		if err == io.EOF {
			break
		}
		if err != nil && err != io.ErrUnexpectedEOF {
			panic(err)
		}
		if p+n > int(codeSize) {
			n = int(codeSize) - p
		}
		p += n
		h.Reset()
		h.Write(buf[:n])
		b := h.Sum(nil)
		outp = puts(outp, b[:])
	}
}
